package wangjg.mda.lang;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;

import wangjg.mda.model.obj.ManyToMany;
import wangjg.mda.model.obj.Method;
import wangjg.mda.model.obj.Model;
import wangjg.mda.model.obj.ModelField;
import wangjg.mda.model.obj.Parameter;
import wangjg.mda.model.obj.Reference;

public class CodeTX extends CodeParser {
	
	public Model getModel() {
		MyClass myClass = this.getMyClass();
		
		Model model = new Model();
		model.setName(myClass.getName());
		model.setParent(myClass.getSuperClass());
		if(myClass.getPackage()!=null){
			model.setPackage(myClass.getPackage().getName());	
		}
		MyAnnotation tableAnno = myClass.getAnnotation("Table");
		//label
		if(myClass.getComment()!=null){
			String label = myClass.getComment().getValue();
			model.setLabel(label);
		}
		//attributes
		model.setAttributes(this.getAttributes(myClass));
		//fields
		model.setFields(this.myFieldsToModelFields(myClass.getFields(), false));
		model.setStaticFields(this.myFieldsToModelFields(myClass.getFields(), true));
		//methods
		model.setMethods(this.myMethodToModelMethod(myClass.getMethods()));
		
		this.handleModelFullName(model);
		this.handleManyToMany(model);
		return model;
	}
	
	protected void handleManyToMany(Model model) {
		this.handleManyToMany(model.getAttributes());
		for(ModelField field:model.getFields()){
			//this.handleManyToMany(field.getAttributes());
			field.setAttribute("ManyToMany", getManyToMany(""+field.getAttribute("ManyToMany")));
		}
	}
	
	private void handleManyToMany(Map attributes) {
		if(attributes==null){
			return;
		}
		List<ManyToMany> mtmList = new ArrayList<ManyToMany>();
		Iterator it = attributes.entrySet().iterator();
		while(it.hasNext()){
			Entry entry = (Entry) it.next();
			if(!"ManyToMany".equals(entry.getKey())){
				continue;
			}
			String ss = entry.getValue()+"";
			String[] s = ss.split(",");
			for(String str:s){
				ManyToMany mtm = this.getManyToMany(str);
				if(mtm!=null){
					mtmList.add(mtm);
				}
			}
			it.remove();
		}
		if(mtmList.size()>0){
			attributes.put("ManyToMany", mtmList);
		}
	}
	
	private ManyToMany getManyToMany(String str){
		ManyToMany mtm = ManyToMany.parse(str);
		if(mtm==null){
			return null;
		}
		mtm.setRelation(getFullTypeName(mtm.getRelation()));
		mtm.setModel(getFullTypeName(mtm.getModel()));
		mtm.setAnotherModel(getFullTypeName(mtm.getAnotherModel()));
		return mtm;
	}

	private List<ModelField> myFieldsToModelFields(List<MyField> fieldList, boolean isStatic) {
		List<ModelField> modelFieldList = new ArrayList<ModelField>();
		for(MyField myField : fieldList){
			if(isStatic != (myField.getModifiers().indexOf("static")!=-1)){
				continue;
			}
			if(isStatic && (myField.getModifiers().indexOf("private")!=-1)){
				continue;
			}
			ModelField modelField = new ModelField();
			modelField.setName(myField.getName());
			modelField.setType(myField.getType());
			if(myField.getAnnotation("Id")!=null){
				modelField.setId(true);
			}
			if(myField.getAnnotation("GeneratedValue")!=null){
				modelField.setAuto(true);
			}
			if(myField.getAnnotation("Transient")!=null){
				modelField.setTransient(true);
			}
			if(myField.getAnnotation("Version")!=null){
				modelField.setVersion(true);	
			}
			modelField.setDefaultValue(myField.getDefaultValue());
			MyAnnotation columnAnno = myField.getAnnotation("Column");
			if(columnAnno!=null){
				if(columnAnno.getParameter("length")!=null){
					String str = (String)columnAnno.getParameter("length");
					if(isInteger(str)){
						modelField.setLength(Integer.parseInt(str));
					}
				}
				if("false".equals(columnAnno.getParameter("nullable"))){
					modelField.setNullable(false);	
				}
				if("true".equals(columnAnno.getParameter("unique"))){
					modelField.setUnique(true);
				}
			}
			
			//label
			if(myField.getComment()!=null){
				String label = myField.getComment()!=null?myField.getComment().getValue():null;
				if(label!=null && label.length()>0){
					modelField.setLabel(label.trim());
				}
			}
			//attributes
			modelField.setAttributes(this.getAttributes(myField));
			modelField.setReference(this.getReference(modelField));
			modelFieldList.add(modelField);
			
			modelField.setAttribute("isStatic", isStatic);
		}
		return modelFieldList;
	}

	private List myMethodToModelMethod(List<MyMethod> myMethodList) {
		List<Method> methodList = new ArrayList<Method>();
		for(MyMethod myMethod : myMethodList){
			Method method = new Method();
			method.setName(myMethod.getName());
			method.setModifiers(myMethod.getModifiers());
			method.setReturnType(myMethod.getReturnType());
			method.setThrowz(myMethod.getThrowz());
			
			method.setParameters(new ArrayList<Parameter>());
			for(MyParameter myParam : myMethod.getParameters()){
				Parameter param = new Parameter();
				param.setName(myParam.getName());
				param.setType(myParam.getType());
				method.getParameters().add(param);
			}
			
			this.setMethodCommentInfo(method, myMethod.getComment());
			
			//attributes
			method.setAttributes(this.getAttributes(myMethod));
			
			methodList.add(method);
		}
		return methodList;
	}
	
	protected void handleModelFullName(Model model) {
		model.setParent(this.getFullTypeName(model.getParent()));
		for(ModelField field:model.getFields()){
			field.setType(this.getFullTypeName(field.getType()));
		}
		for(Method method:model.getMethods()){
			method.setReturnType(this.getFullTypeName(method.getReturnType()));
			for(Parameter parameter:method.getParameters()){
				parameter.setType(this.getFullTypeName(parameter.getType()));
			}
		}
	}

	private boolean isInteger(String str){
		return checkPattern("^\\d+$", str);
	}

	protected Reference getReference(ModelField field) {
		String text = (String) field.getAttribute("ref");
		if(text==null){return null;}
		final String REG_COMMENT_REF = "([a-z_A-Z\\$](?:\\.?\\w)*)?(?:#([a-zA-Z_0-9\\$\\*]+))?(?:(\\()([^\\)\\}]*)(\\)))?(?: (.*))?";
		Matcher matcher = this.getMatcher(text, REG_COMMENT_REF);
		while(matcher.find()){
			MyCommentLink link = this.__getMyCommentLink(matcher);
			if(link.isMethod()){
				break;
			}
			Reference reference = new Reference();
			String name = this.getFullTypeName(link.getType());
			if(name==null){
				name = this.getMyClass().getFullname();//TODO not good
			}
			reference.setModelName(name);
			reference.setFiledName(link.getMemberName());
			reference.setLabelName(link.getLabel());
			return reference;
		}
		return null;
	}

	protected Map getAttributes(){
		MyClass myClass = this.getMyClass();
		return this.getAttributes(myClass);
	}
	
	Map getAttributes(MyClass myClass){
		Map attributes = new LinkedHashMap();
		List<MyDocParameter> params = null;
		if(myClass.getComment()!=null){
			params = myClass.getComment().getParams();
		}
		List<MyAnnotation> annos = myClass.getAnnos();
		this.getAttributes(params, annos, attributes);
		return attributes;
	}

	Map getAttributes(MyField myField){
		Map attributes = new LinkedHashMap();
		List<MyDocParameter> params = null;
		if(myField.getComment()!=null){
			params = myField.getComment().getParams();
		}
		List<MyAnnotation> annos = myField.getAnnos();
		this.getAttributes(params, annos, attributes);
		return attributes;
	}
	
	Map getAttributes(MyMethod myMethod){
		Map attributes = new LinkedHashMap();
		List<MyDocParameter> params = null;
		if(myMethod.getComment()!=null){
			params = myMethod.getComment().getParams();
		}
		List<MyAnnotation> annos = myMethod.getAnnos();
		this.getAttributes(params, annos, attributes);
		return attributes;
	}
	
	private void getAttributes(List<MyDocParameter> params, List<MyAnnotation> annos, Map attributes){
		if(params!=null){
			for(MyDocParameter param : params){
				String value = (param.getTag()!=null?param.getTag():"")+param.getValue();
				if(attributes.get(param.getType())==null){
					attributes.put(param.getType(), value);
				}else{
					attributes.put(param.getType(), attributes.get(param.getType())+","+value);
				}
			}			
		}
		if(annos!=null){
			for(MyAnnotation myAnno : annos){
				attributes.put(myAnno.getName(), myAnno.getParameters());
			}
		}
	}

	private void testKV(){
		Matcher matcher = this.getMatcher("(strategy = GenerationType.SEQUENCE, generator = \"gggg\", length=20)\nabc\n123", REG_KEYVALUE);
		int n=0;
		while(matcher.find()){
			System.out.println(++n);
			this._printMatcher(matcher);
		}
	}
	
}
